package org.micode.libs.webflux.base;

import org.micode.common.annotation.NoNeedLogin;
import org.micode.common.annotation.PermissionsFlag;
import org.micode.common.domain.base.BaseUserCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Slf4j
@Configuration
@Order(-1)
public abstract class BaseWebFluxWebFilter<T extends BaseUserCache> implements WebFilter {

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        long startTime = System.currentTimeMillis();
        if (HttpMethod.OPTIONS.equals(exchange.getRequest().getMethod())) {
            ServerHttpRequest authOkReq = exchange.getRequest().mutate()
                    .path(fillUrlByRoot("/auth/ok")).build();
            ServerWebExchange authOkExchange = exchange.mutate().request(authOkReq).build();
            return chain.filter(authOkExchange);
        }

        log.info("API-begin: {}", exchange.getRequest().getURI());

        if (notNeedLogin(exchange)) {
            // 不需要登录时，直接返回
            return doFinally(exchange, chain, startTime);
        }

        // 判断用户是否登录，如果没有登录，则不能执行
        T userCache;
        try {
            userCache = checkPermission(exchange);
        } catch (Exception e) {
            log.warn("Handling permission detection exception", e);
            userCache = null;
        }

        if (null != userCache) {
            //添加请求属性key和value
            exchange.getAttributes().put(getUserIdKey(), userCache);
            return doFinally(exchange, chain, startTime);
        } else {
            log.warn("No login or no access!");

            // http头中没有appKey，修改请求的目标url为/auth/error
            // request.mutate返回一个请求构建器(builder design pattern)，path方法修改请求的url，build方法返回新的request
            ServerHttpRequest authErrorReq = exchange.getRequest().mutate()
                    .path(fillUrlByRoot("/auth/invalidToken")).build();
            if (HttpMethod.GET.equals(exchange.getRequest().getMethod())) {
                authErrorReq = exchange.getRequest().mutate()
                        .path(fillUrlByRoot("/auth/invalidTokenForGet")).build();
            }
            // 构建一个新的ServerWebExchange
            ServerWebExchange authErrorExchange = exchange.mutate().request(authErrorReq).build();

            return chain.filter(authErrorExchange);
        }
    }

    protected abstract String fillUrlByRoot(String s);

    protected abstract String getUserIdKey();

    protected abstract T checkPermission(ServerWebExchange exchange);

    private Mono<Void> doFinally(ServerWebExchange exchange, WebFilterChain chain, long startTime) {
        return chain.filter(exchange)
                .doOnError(e -> {
                    log.warn("Exception calling API: {}", e.getMessage(), e);
                })
                .doFinally(signalType -> {
                    long totalTime = System.currentTimeMillis() - startTime;
                    log.info("API-end: ms={}，{}", totalTime, exchange.getRequest().getURI());
                });
    }

    private boolean notNeedLogin(ServerWebExchange exchange) {
        Object handlerMethod = this.requestMappingHandlerMapping
                .getHandler(exchange).toProcessor().peek();
        if (handlerMethod instanceof HandlerMethod) {
            NoNeedLogin noNeedLogin = ((HandlerMethod) handlerMethod).getMethodAnnotation(NoNeedLogin.class);
            // 设置了 NoNeedLogin 标识，则返回true
            return noNeedLogin != null;
        } else {
            return false;
        }
    }

    protected String getPermissionsFlag(ServerWebExchange exchange) {
        Object handlerMethod = this.requestMappingHandlerMapping
                .getHandler(exchange).toProcessor().peek();
        if (handlerMethod instanceof HandlerMethod) {
            PermissionsFlag permissionsFlag = ((HandlerMethod) handlerMethod).getMethodAnnotation(PermissionsFlag.class);
            // 设置了 NoNeedLogin 标识，则返回true
            return permissionsFlag.value();
        } else {
            return null;
        }
    }

    protected String getToken(ServerWebExchange exchange) {
        String token = getTokenByKey(exchange, "token");
        return StringUtils.isAnyEmpty(token)
                ? getTokenByKey(exchange, "X-TOKEN")
                : token;
    }

    private String getTokenByKey(ServerWebExchange exchange, String keyName) {
        String token = exchange.getRequest().getQueryParams().getFirst(keyName);
        token = StringUtils.isAnyEmpty(token)
                ? exchange.getRequest().getHeaders().getFirst(keyName)
                : token;
        if (StringUtils.isAnyEmpty(token)) {
            if (null != exchange.getRequest().getCookies()) {
                HttpCookie cookie = exchange.getRequest().getCookies().getFirst(keyName);
                token = null == cookie ? null : cookie.getValue();
            }
        }
        return token;
    }

}
